Published on

Developing a Python Library for Symbolic Computation and Pedagogy | Getting Started

Authors

Alright, let's get into some coding. If you missed the backstory, check out the previous post here: Developing a Python Library for Symbolic Computation and Pedagogy | Litle Struggle.

The file structure might be change in the future, but for now, it will be like this:

mathemapy 
    - core
        __init__.py
        expr.py
        add.py
        sub.py
        mul.py
        div.py
        pow.py
        numbers.py
        symbol.py
         
    __init__.py

I'll expand on this in the future as this continues. I won't be filling the __init__.py files here; just look into the GitHub code if needed. I'm going to build this with a parent class of Expression. Every other class will be a child of this parent class.

# core/expr.py

from abc import ABC, abstractmethod

class Expression(ABC):
    """
    Expression class is the parent for all other classes
    """
    @abstractmethod
    def evaluate(self):
        pass

    @abstractmethod
    def simplify(self):
        pass

    @abstractmethod
    def __str__(self):
        pass

    def __repr__(self):
        return self.__str__()
    
    def __add__(self, other):
        from .add import Add
        return Add(self, other)
    
    def __sub__(self, other):
        from .sub import Sub
        return Sub(self, other)
    
    def __mul__(self, other):
        from .mul import Mul
        return Mul(self, other)
    
    def __truediv__(self, other):
        from .div import Div
        return Div(self, other)
    
    def __pow__(self, other):
        from .pow import Pow
        return Pow(self, other)

    def __eq__(self, other: 'Expression') -> bool:
        if not isinstance(other, Expression):
            return False
        # Compare simplified forms
        return str(self.simplify()) == str(other.simplify())

    def __hash__(self):
        return hash(str(self.simplify()))

Let me explain,

  • ABC and abstractmethod are imported from Python's built-in abc module.

  • evaluate and simplify are the main methods that will be defined in each child class.

  • Dunder methods (like __add__) are inherited by each class, allowing us to use Python's built-in parsing to convert expressions into classes seamlessly. This eliminates the need for manual parsing for now (note that SymPy supports both methods).

  • __str__ and __repr__ define how each operation and operand is printed. Each child class will define its __str__ method.

  • Because we define the __eq__ method, we also need __hash__. This is relatively straightforward. In __eq__, we check if the simplified forms are equal, not the evaluated forms. This is because evaluation can sometimes return Python objects instead of our custom-defined objects, making it harder to maintain. Therefore, we use the simplify method.

    Later in __eq__, we'll need to handle alternative representations of the same expressions, but for now, we'll keep it simple.

That's all for this post. In the next one, I'll document both the Number and Symbol classes found in numbers.py and symbol.py.

Code is on my GitHub: https://github.com/RezSat/mathemapy

Thank you for reading!